package com.sap.psr.vulas.cia.util;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;

import com.sap.psr.vulas.cia.model.nexus.NexusArtifact;
import com.sap.psr.vulas.cia.model.nexus.NexusArtifactInfoResourceResponse;
import com.sap.psr.vulas.cia.model.nexus.NexusArtifactResolution;
import com.sap.psr.vulas.cia.model.nexus.NexusResolvedArtifact;
import com.sap.psr.vulas.cia.model.nexus.NexusSearchNGResponse;
import com.sap.psr.vulas.cia.util.ArtifactDownloader.DefaultRequestCallback;
import com.sap.psr.vulas.cia.util.ArtifactDownloader.FileResponseExtractor;
import com.sap.psr.vulas.shared.enums.ProgrammingLanguage;
import com.sap.psr.vulas.shared.json.model.Artifact;
import com.sap.psr.vulas.shared.util.VulasConfiguration;

public class NexusWrapper implements RepositoryWrapper {

	
	private static Logger log = LoggerFactory.getLogger(NexusWrapper.class);
	
	private static String searchUrl;
	private static String serviceUrl;
	private static String timestampUrl;
	
	private static boolean CONFIGURED = false;
	
	private static Set<ProgrammingLanguage> SUPP_LANG = new HashSet<ProgrammingLanguage>();
	
	static {
		SUPP_LANG.add(ProgrammingLanguage.JAVA);
		searchUrl =  VulasConfiguration.getGlobal().getConfiguration().getString("vulas.lib-utils.nexus.search", null);
		serviceUrl = VulasConfiguration.getGlobal().getConfiguration().getString("vulas.lib-utils.nexus.service", null);
		timestampUrl = VulasConfiguration.getGlobal().getConfiguration().getString("vulas.lib-utils.nexus.servicerepo", null);
		if(searchUrl!=null && serviceUrl!=null && timestampUrl!=null)
			CONFIGURED=true;
	}
	
	@Override
	public Set<ProgrammingLanguage> getSupportedLanguages() {
		return SUPP_LANG;
	}
	
	private NexusSearchNGResponse searchInNexus(String _g, String _a, String _v, String _c, String _p, String _sha1) throws InterruptedException{
		Map<String,String> params = new HashMap<String,String>();
		params.put("g", (_g!=null)?_g:"");
		params.put("a", (_a!=null)?_a:"");
		params.put("v", (_v!=null)?_v:"");
		params.put("p", (_p!=null)?_p:"");
		params.put("c", (_c!=null)?_c:"");
		params.put("sha1", (_sha1!=null)?_sha1:"");
		
		final RestTemplate rest_template = new RestTemplate();
		ResponseEntity<NexusSearchNGResponse> responseEntity = null;
		
		if(this.searchUrl==null){
			log.warn("No url configured for searching in Nexus, skip NexusWrapper");
		} else {
			try{
				responseEntity = rest_template.getForEntity(searchUrl, NexusSearchNGResponse.class, params);
			}
			catch(HttpServerErrorException he){
				NexusWrapper.log.error("HttpServerErrorException: Received status ["+he.getStatusCode()+"] calling url ["+searchUrl+"]");
			}
			catch(HttpClientErrorException he){
				NexusWrapper.log.warn("HttpClientErrorException: Received status ["+he.getStatusCode()+"] calling url ["+searchUrl+"]");
			}
		}
		if(responseEntity!=null && responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody()!=null)
			return responseEntity.getBody();
		return null;
	}
	

	private Artifact resolveInNexus(String group, String artifact, String version, String classifier, String packaging) {
		final Map<String,String> params = new HashMap<String,String>();
		params.put("g", group);
		params.put("a", artifact);
		params.put("v", version);
		params.put("p", packaging);
		params.put("cl", classifier);
		params.put("action", "resolve");
		params.put("r", "build.snapshots");
		
		NexusResolvedArtifact response = null;
		
		final RestTemplate rest_template = new RestTemplate();
		ResponseEntity<NexusArtifactResolution> responseEntity = null;
		
		if(this.serviceUrl==null){
			log.warn("No url configured for resolving artifact in Nexus, skip NexusWrapper");
		} else {
			try{
				responseEntity = rest_template.getForEntity(serviceUrl, NexusArtifactResolution.class, params);
			}
			catch(HttpServerErrorException he){
				NexusWrapper.log.error("Received internal server error ["+he.getStatusCode()+ "] for url ["+serviceUrl+"] with params ["+ params.get("g")+":"+ params.get("a")+":"+ params.get("v")+"]");
			}
			catch(HttpClientErrorException he){
				NexusWrapper.log.error("Http Client exception ["+he.getStatusCode()+ "] received when calling url ["+serviceUrl+"]with params ["+ params.get("g")+":"+ params.get("a")+":"+ params.get("v")+":"+ params.get("cl")+":"+ params.get("p")+"]");
			}
		}
		if(responseEntity!=null && responseEntity.getBody()!=null)
			response = responseEntity.getBody().getData();
		Artifact result = null;
		if(response!=null){
			result = new Artifact(response.getGroupId(),response.getArtifactId(),response.getVersion());
			result.setProgrammingLanguage(ProgrammingLanguage.JAVA);
			ResponseEntity<NexusArtifactInfoResourceResponse> responseForTimestamp = null;
			
			if(this.timestampUrl==null){
				log.warn("No url configured for retriving artifact timestamp in Nexus, skip NexusWrapper");
			} else {
				final Map<String,String> params1 = new HashMap<String,String>();
				params1.put("artifact", response.getRepositoryPath());
				try{
					responseForTimestamp = rest_template.getForEntity(timestampUrl, NexusArtifactInfoResourceResponse.class, params1);
				}
				catch(HttpServerErrorException he){
					NexusWrapper.log.error("Received internal server error ["+he.getStatusCode()+ "] for url ["+responseForTimestamp+"]");
				}
				catch(HttpClientErrorException he){
					NexusWrapper.log.error("Http Client exception ["+he.getStatusCode()+ "] received when calling url ["+responseForTimestamp+"]");
				}
			}
			if(responseForTimestamp!=null && responseForTimestamp.getBody()!=null)
				result.setTimestamp(responseForTimestamp.getBody().getData().getUploaded());
		
		}
		return result; 
	}

	
	
	
	
	@Override
	public Set<Artifact> getAllArtifactVersions(String group, String artifact, String classifier, String packaging) throws InterruptedException {
		
		final NexusSearchNGResponse nexusResp = this.searchInNexus(group, artifact, "", classifier, packaging, null);
		
		Set<Artifact> result = new TreeSet<Artifact>();
		
		if(nexusResp!=null && nexusResp.getData()!=null && nexusResp.getData().getArtifactList()!=null ){
			for(NexusArtifact a : nexusResp.getData().getArtifactList()){
				//TODO (2018-04-19): To get the timestamp we would need an additional get for each artifact (the same done in getArtifactVersion)
				result.add(new Artifact(a.getGroupId(), a.getArtifactId(), a.getVersion()));
			}
		}
		else{
			log.info("No artifact found for group [" + group + "], artifact ["+artifact+"], classifier ["+classifier+"], packaging ["+packaging+"] in Nexus");
		}
		
		return result;
		
		
	}

	@Override
	public Set<Artifact> getGreaterArtifactVersions(String group, String artifact, String greaterThanVersion,
			String classifier, String packaging) throws Exception {
		
		Set<Artifact> all = this.getAllArtifactVersions(group, artifact, classifier, packaging);
		
		Set<Artifact> allWithTimestamp = new TreeSet<Artifact>();
		Artifact toCompare = null;
		for(Artifact a : all){
			if(a.getLibId().getVersion().equals(greaterThanVersion))
				toCompare = this.getArtifactVersion(a.getLibId().getMvnGroup(), a.getLibId().getArtifact(), a.getLibId().getVersion(), null, null, null);
			else
				allWithTimestamp.add(this.getArtifactVersion(a.getLibId().getMvnGroup(), a.getLibId().getArtifact(), a.getLibId().getVersion(), null, null, null));
		}
		// Return bad request if version is not found
		if(toCompare==null){
			log.error("Version [" + greaterThanVersion + "] not found in Nexus for group [" + group + "] and artifact [" + artifact + "]");
			return null;
		}
				
		Set<Artifact> greater = new TreeSet<Artifact>();
		
		for (Artifact t: allWithTimestamp){
			if (t.compareTo(toCompare)>0)
				greater.add(t);
		}
		
		return greater;
		
	}

	@Override
	public Artifact getLatestArtifactVersion(String group, String artifact, String classifier, String packaging) {
		return this.resolveInNexus(group, artifact, "RELEASE", classifier, packaging);
		 
	}

	@Override
	public Artifact getArtifactVersion(String group, String artifact, String version, String classifier,
			String packaging,ProgrammingLanguage lang) throws Exception {
		return this.resolveInNexus(group, artifact, version, classifier, packaging);
	}

	@Override
	public Path downloadArtifact(Artifact a) throws Exception {
		final Map<String,String> params = new HashMap<String,String>();
		params.put("g", a.getLibId().getMvnGroup());
		params.put("a", a.getLibId().getArtifact());
		params.put("v", a.getLibId().getVersion());
		params.put("p", a.getPackaging());
		params.put("cl", a.getClassifier());
		params.put("action", "content");
		params.put("r", "build.snapshots");
		
		final RestTemplate rest_template = new RestTemplate();
		Path result = null;
		if(this.serviceUrl==null){
			log.warn("No url configured for downloading artifact from Nexus, skip NexusWrapper");
		} else {
			try{
				rest_template.execute(serviceUrl, HttpMethod.GET, new DefaultRequestCallback(), new FileResponseExtractor(a, a.getAbsM2Path()),params);
				result = a.getAbsM2Path();
			}
			catch(HttpServerErrorException he){
				//exception thrown when 5xx is received
				NexusWrapper.log.error("Received code ["+he.getStatusCode()+ "] for url ["+serviceUrl+"] while downloading artifact ["+a.toString()+"]");
			}
			catch(HttpClientErrorException he){
				//exception thrown when 4xx is received
				NexusWrapper.log.warn("Received code ["+he.getStatusCode()+ "] downloading artifact ["+a.toString()+"] from url ["+serviceUrl+"]");
			}
		}
		return result;
	}

	@Override
	public Artifact getArtifactForDigest(String digest) throws InterruptedException {
		//currently it only returns the lib id, to check whether package classifier and timestamp can be obtained (if needed)
		
		NexusSearchNGResponse nexusResp = this.searchInNexus(null, null, null, null, null, digest.toLowerCase());
			
		Artifact result = null;
		if(nexusResp.getData()!=null && nexusResp.getData().getArtifactList()!=null && nexusResp.getData().getArtifactList().size()==1){
			result = new Artifact(nexusResp.getData().getArtifactList().get(0).getGroupId(), nexusResp.getData().getArtifactList().get(0).getArtifactId(), nexusResp.getData().getArtifactList().get(0).getVersion());
			result.setProgrammingLanguage(ProgrammingLanguage.JAVA);
		}
		else if(nexusResp.getData().getArtifactList()!=null && nexusResp.getData().getArtifactList().size()>1) {
			log.error("More than one artifact found for SHA1 [" + digest + "] in Nexus");
		} else {
			log.info("No artifact found for SHA1 [" + digest + "] in Nexus");
		}

		return result;
		
	}

	@Override
	public boolean isConfigured() {
		return CONFIGURED;
	}
	
		
}
